home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.1 (Developer) [x86] / NeXT Step 3.1 Intel dev.cdr.dmg / NextDeveloper / Examples / AppKit / Draw / gvLinks.m < prev    next >
Text File  |  1993-01-06  |  18KB  |  651 lines

  1. #import "draw.h"
  2.  
  3. @implementation GraphicView(Links)
  4.  
  5. /* See the Links.rtf file for overview about Object Links in Draw. */
  6.  
  7. #define BUFFER_SIZE 1100
  8. #define INT_WIDTH 11
  9.  
  10. /*
  11.  * Returns an NXSelection describe the current Graphic's selected in the view.
  12.  * If the user did Select All, then the gvFlags.selectAll bit is set and we
  13.  * return the allSelection NXSelection.  If the user dragged out a rectangle,
  14.  * then the dragRect rectangle is set and we return a ByRect NXSelection.
  15.  * Otherwise, we return a ByGraphic NXSelection.
  16.  *
  17.  * We use the writeIdentifierTo: mechanism so that Group Graphic's can ask
  18.  * their components to write out all their identifiers so that we have a
  19.  * maximal chance of getting all the objects.
  20.  */
  21.  
  22. - (NXSelection *)currentSelection
  23. {
  24.     NXRect sbounds;
  25.     int i, graphicCount;
  26.     NXSelection *retval = nil;
  27.     char *s, *selbuf, buffer[BUFFER_SIZE];
  28.  
  29.     if (![slist count]) return [NXSelection emptySelection];
  30.  
  31.     if (gvFlags.selectAll) {
  32.     if ([slist count] == [glist count]) {
  33.         return [NXSelection allSelection];
  34.     } else {
  35.         gvFlags.selectAll = NO;
  36.     }
  37.     }
  38.  
  39.     if (dragRect) {
  40.     sbounds = *dragRect;
  41.     sprintf(buffer, "%d %d %d %d %d", ByRect, 
  42.         (int)sbounds.origin.x, (int)sbounds.origin.y,
  43.         (int)(sbounds.size.width+0.5), (int)(sbounds.size.height+0.5));
  44.     selbuf = buffer;
  45.     } else {
  46.     graphicCount = 0;
  47.     i = [slist count];
  48.     while (i--) graphicCount += [[slist objectAt:i] graphicCount];
  49.     if (graphicCount > (BUFFER_SIZE / INT_WIDTH)) {
  50.         NX_MALLOC(selbuf, char, graphicCount * INT_WIDTH);
  51.     } else {
  52.         selbuf = buffer;
  53.     }
  54.     sprintf(buffer, "%d %d", ByList, graphicCount);
  55.     s = selbuf + strlen(selbuf);
  56.     i = [slist count];
  57.     while (i--) {
  58.         *s++ = ' ';
  59.         [[slist objectAt:i] writeIdentifierTo:s];
  60.         s += strlen(s);
  61.     }
  62.     }
  63.  
  64.     retval = [[NXSelection allocFromZone:[self zone]] initWithDescription:selbuf length:strlen(selbuf)+1];
  65.  
  66.     if (selbuf != buffer) free(selbuf);
  67.  
  68.     return retval;
  69. }
  70.  
  71. /*
  72.  * Used for destination selections only.
  73.  * Just extracts the unique identifier for the destination Image
  74.  * or TextGraphic and then searches through the glist to find that
  75.  * Graphic and returns it.
  76.  *
  77.  * Again, we use the graphicIdentifiedBy: mechanism so that we
  78.  * descend into Group's of Graphics to find a destination.
  79.  */
  80.  
  81. - (Graphic *)findGraphicInSelection:(NXSelection *)selection
  82. {
  83.     int i;
  84.     Graphic *graphic;
  85.     const char *selectionInfo;
  86.     int selectionInfoLength, identifier, selectionType;
  87.  
  88.     selectionInfo = [selection descriptionOfLength:&selectionInfoLength];
  89.     if (selectionInfo) {
  90.     sscanf(selectionInfo, "%d %d", &selectionType, &identifier);
  91.     if (selectionType == ByGraphic) {
  92.         for (i = [glist count]-1; i >= 0; i--) {
  93.         if (graphic = [[glist objectAt:i] graphicIdentifiedBy:identifier]) return graphic;
  94.         }
  95.     }
  96.     }
  97.  
  98.     return nil;
  99. }
  100.  
  101. /*
  102.  * Returns YES and theRect is valid only if the selection is one which
  103.  * the user created by dragging out a rectangle.
  104.  */
  105.  
  106. - (BOOL)getRect:(NXRect *)theRect forSelection:(NXSelection *)selection
  107. {
  108.     NXRect stackRect;
  109.     const char *selectionInfo;
  110.     int selectionInfoLength;
  111.     DrawSelectionType selectionType;
  112.  
  113.     if (selectionInfo = [selection descriptionOfLength:&selectionInfoLength]) {
  114.     if (!theRect) theRect = &stackRect;
  115.     sscanf(selectionInfo, "%d %f %f %f %f", (int *)&selectionType,
  116.         &(theRect->origin.x), &(theRect->origin.y),
  117.         &(theRect->size.width), &(theRect->size.height));
  118.     if (selectionType == ByRect) return YES;
  119.     }
  120.  
  121.     return NO;
  122. }
  123.  
  124. /*
  125.  * For source selections only.
  126.  * Returns the list of Graphics in the current document which were
  127.  * in the selection passed to this method.  Note that any Group 
  128.  * which includes a Graphic in the passed selection will be included
  129.  * in its entirety.
  130.  */
  131.  
  132. - (List *)findGraphicsInSelection:(NXSelection *)selection
  133. {
  134.     int i, count;
  135.     Graphic *graphic;
  136.     List *list = nil;
  137.     NXRect sBounds, gBounds;
  138.     int selectionInfoLength;
  139.     const char *s, *theGraphics;
  140.     DrawSelectionType selectionType;
  141.  
  142.     if ([selection isEqual:[NXSelection allSelection]]) {
  143.     count = [glist count];
  144.     list = [[List allocFromZone:[self zone]] initCount:count];
  145.     for (i = 0; i < count; i++) [list addObject:[glist objectAt:i]];
  146.     } else if ([self getRect:&sBounds forSelection:selection]) {
  147.     count = [glist count];
  148.     list = [[List allocFromZone:[self zone]] init];
  149.     for (i = 0; i < count; i++) {
  150.         graphic = [glist objectAt:i];
  151.         [graphic getBounds:&gBounds];
  152.         NXInsetRect(&gBounds, -0.1, -0.1);
  153.         if (NXIntersectsRect(&gBounds, &sBounds)) [list addObject:graphic];
  154.     }
  155.     } else if (s = [selection descriptionOfLength:&selectionInfoLength]) {
  156.     sscanf(s, "%d %d", (int *)&selectionType, &count);
  157.     if (selectionType == ByList) {
  158.         if (s = strchr(s, ' ')) s = strchr(s+1, ' ');
  159.         if (s++) {
  160.         theGraphics = s;
  161.         list = [[List allocFromZone:[self zone]] init];
  162.         count = [glist count];
  163.         for (i = 0; i < count; i++) {
  164.             graphic = [glist objectAt:i];
  165.             s = theGraphics;
  166.             while (s && *s) {
  167.             if ([graphic graphicIdentifiedBy:atoi(s)]) {
  168.                 [list addObject:graphic];
  169.                 break;
  170.             }
  171.             if (s = strchr(s, ' ')) s++;
  172.             }
  173.         }
  174.         }
  175.     }
  176.     }
  177.  
  178.     if (![list count]) {
  179.     [list free];
  180.     list = nil;
  181.     }
  182.  
  183.     return list;
  184. }
  185.  
  186. /*
  187.  * Importing/Exporting links.
  188.  */
  189.  
  190. /*
  191.  * This method is called by copyToPasteboard:.  It just puts a link to the currentSelection
  192.  * (presumably just written to the pasteboard by copyToPasteboard:) into the specified
  193.  * pboard.  Note that it only does all this if we are writing all possible types to the
  194.  * pasteboard (typesList == NULL) or if we explicitly ask for the link to be written
  195.  * (typesList includes NXDataLinkPboardType).
  196.  */
  197.  
  198. - writeLinkToPasteboard:(Pasteboard *)pboard types:(const NXAtom *)typesList
  199. {
  200.     NXDataLink *link;
  201.  
  202.     if (linkManager && (!typesList || IncludesType(typesList, NXDataLinkPboardType))) {
  203.     if (link = [[NXDataLink alloc] initLinkedToSourceSelection:[self currentSelection] managedBy:linkManager supportingTypes:TypesDrawExports() count:NUM_TYPES_DRAW_EXPORTS]) {
  204.         [pboard addTypes:&NXDataLinkPboardType num:1 owner:[self class]];
  205.         [link writeToPasteboard:pboard];
  206.         [link free];
  207.     }
  208.     [linkManager writeLinksToPasteboard:pboard]; // for embedded linked things
  209.     }
  210.  
  211.     return self;
  212. }
  213.  
  214. /*
  215.  * Sets up a link from the Draw document to another document.
  216.  * This is called by the drag stuff (gvDrag.m) and the normal copy/paste stuff (gvPasteboard.m).
  217.  * We allow for the case of graphic being nil as long as the link is capable of supplying
  218.  * data of a type we can handle (currently Text or Image).
  219.  */
  220.  
  221. - (BOOL)addLink:(NXDataLink *)link toGraphic:(Graphic *)graphic at:(const NXPoint *)p update:(int)update
  222. {
  223.     NXSelection *selection = nil;
  224.  
  225.     if (!graphic && link && update != UPDATE_NEVER) {
  226.     if (TextPasteType([link types])) {
  227.         graphic = [[TextGraphic allocFromZone:[self zone]] initEmpty];
  228.     } else if (MatchTypes([link types], [NXImage imagePasteboardTypes])) {
  229.         graphic = [[Image allocFromZone:[self zone]] initEmpty];
  230.     }
  231.     update = UPDATE_IMMEDIATELY;
  232.     }
  233.  
  234.     if (graphic && link) {
  235.     selection = [graphic selection];
  236.     if ([linkManager addLink:link at:selection]) {
  237.         if (!update) [link setUpdateMode:NX_UpdateNever];
  238.         [graphic setLink:link];
  239.         if (graphic = [self placeGraphic:graphic at:p]) {
  240.         if (update == UPDATE_IMMEDIATELY) {
  241.             [link updateDestination];
  242.             graphic = [self findGraphicInSelection:selection];
  243.             if (![graphic isValid]) {
  244.             NXRunLocalizedAlertPanel(NULL, "Import Link",
  245.                 "Unable to import linked data.", NULL, NULL, NULL,
  246.                 "Message given to user when import of linked data fails.");
  247.             [self removeGraphic:graphic];
  248.             } else {
  249.             return YES;
  250.             }
  251.         } else {
  252.             return YES;
  253.         }
  254.         }
  255.     }
  256.     }
  257.  
  258.     [link free];
  259.     [selection free];
  260.     [graphic free];
  261.  
  262.     return NO;
  263. }
  264.  
  265. /*
  266.  * Keeping links up to date.
  267.  * These methods are called either to update a link that draw has to another
  268.  * document or to cause Draw to update another document that is linked to it.
  269.  */
  270.  
  271. /*
  272.  * Sent whenever NeXTSTEP wants us to update some data in our document which
  273.  * we get by being linked to some other document.
  274.  */
  275.  
  276. - pasteFromPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection
  277. {
  278.     id graphic;
  279.     NXRect gBounds;
  280.  
  281.     if (graphic = [self findGraphicInSelection:selection]) {
  282.     gBounds = [graphic reinitFromPasteboard:pboard];
  283.     [self cache:&gBounds];    // updating a destination link
  284.     [window flushWindow];
  285.     [self dirty];
  286.     return self;
  287.     }
  288.  
  289.     return nil;
  290. }
  291.  
  292. /*
  293.  * Lazy pasteboard method for cheapCopyAllowed case ONLY.
  294.  * See copyToPasteboard:at:cheapCopyAllowed: below.
  295.  */
  296.  
  297. - pasteboard:(Pasteboard *)sender provideData:(const char *)type
  298. {
  299.     List *list;
  300.     NXStream *stream;
  301.     NXSelection *selection;
  302.  
  303.     selection = [[NXSelection allocFromZone:[self zone]] initFromPasteboard:sender];
  304.     list = [self findGraphicsInSelection:selection];
  305.     if (list && (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY))) {
  306.     if (type == NXPostScriptPboardType) {
  307.         [self writePSToStream:stream usingList:list];
  308.     } else if (type == NXTIFFPboardType) {
  309.         [self writeTIFFToStream:stream usingList:list];
  310.     }
  311.     [sender writeType:type fromStream:stream];
  312.     NXCloseMemory(stream, NX_FREEBUFFER);
  313.     }
  314.     [list free];
  315.     [selection free];
  316.  
  317.     return self;
  318. }
  319.  
  320. /*
  321.  * Called by NeXTSTEP when some other document needs to be updated because
  322.  * they are linked to something in our document.
  323.  */
  324.  
  325. - copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)cheapCopyAllowed
  326. {
  327.     List *list;
  328.     NXStream *stream;
  329.     NXTypedStream *ts;
  330.     id retval = self;
  331.     const char *types[3];
  332.  
  333.     types[1] = NXPostScriptPboardType;
  334.     types[2] = NXTIFFPboardType;
  335.  
  336.     if (cheapCopyAllowed) {
  337.     if (list = [self findGraphicsInSelection:selection]) {
  338.         types[0] = NXSelectionPboardType;
  339.         [pboard declareTypes:types num:3 owner:self];
  340.         [selection writeToPasteboard:pboard];
  341.     } else {
  342.         retval = nil;
  343.     }
  344.     [list free];
  345.     } else {
  346.     types[0] = DrawPboardType;
  347.     [pboard declareTypes:types num:3 owner:[self class]];
  348.     list = [self findGraphicsInSelection:selection];
  349.     if (list && (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY))) {
  350.         if (ts = NXOpenTypedStream(stream, NX_WRITEONLY)) {
  351.         NXWriteRootObject(ts, list);
  352.         NXCloseTypedStream(ts);
  353.         }
  354.         [pboard writeType:DrawPboardType fromStream:stream];
  355.         NXCloseMemory(stream, NX_FREEBUFFER);
  356.     } else {
  357.         retval = nil;
  358.     }
  359.     [list free];
  360.     }
  361.  
  362.     return retval;
  363. }
  364.  
  365.  
  366. /*
  367.  * Supports linking to an entire file (not just a selection therein).
  368.  * This occurs when you drag a file into Draw and link (see gvDrag).
  369.  * This is very analogous to the pasteFromPasteboard:at: above.
  370.  */
  371.  
  372. - importFile:(const char *)filename at:(NXSelection *)selection
  373. {
  374.     id graphic;
  375.     NXRect gBounds;
  376.  
  377.     if (graphic = [self findGraphicInSelection:selection]) {
  378.     gBounds = [graphic reinitFromFile:filename];
  379.     [self cache:&gBounds];    // updating a link to an imported file
  380.     [window flushWindow];
  381.     [self dirty];
  382.     return self;
  383.     }
  384.  
  385.     return nil;
  386. }
  387.  
  388. /* Other Links methods */
  389.  
  390. /*
  391.  * Just makes the Link Inspector panel reflect whether any of the
  392.  * Graphic's currently selected are linked to some other document.
  393.  */
  394.  
  395. - updateLinksPanel
  396. {
  397.     int i, linkCount = 0;
  398.     Graphic *foundGraphic = nil, *graphic = nil;
  399.  
  400.     if (linkManager) {
  401.     for (i = [slist count]-1; i >= 0; i--) {
  402.         if (graphic = [[slist objectAt:i] graphicLinkedBy:NULL]) {
  403.         if ([graphic isKindOf:[Group class]]) {
  404.             linkCount += 2;
  405.             break;
  406.         } else {
  407.             linkCount += 1;
  408.             foundGraphic = graphic;
  409.         }
  410.         }
  411.     }
  412.     if (linkCount == 1) {
  413.         [NXDataLinkPanel setLink:[foundGraphic link] andManager:linkManager isMultiple:NO];
  414.     } else if (linkCount) {
  415.         [NXDataLinkPanel setLink:[foundGraphic link] andManager:linkManager isMultiple:YES];
  416.     } else {
  417.         [NXDataLinkPanel setLink:nil andManager:linkManager isMultiple:NO];
  418.     }
  419.     }
  420.  
  421.     return self;
  422. }
  423.  
  424. - (NXDataLinkManager *)linkManager
  425. {
  426.     return linkManager;
  427. }
  428.  
  429. /*
  430.  * When we get a linkManager via this method, we must go and revive all the links.
  431.  * This is due to the fact that we don't archive ANY link information when we
  432.  * save a Draw document.  However, the unique identifiers ARE archived, and thus,
  433.  * when we unarchive, we can recreate NXSelections with those unique identifiers
  434.  * and then ask the NXDataLinkManager for the link objects associated with those
  435.  * NXSelections.
  436.  *
  437.  * After we have revived all the links, we call breakLinkAndRedrawOutlines:
  438.  * with nil (meaning redraw the link outlines for all links).
  439.  */
  440.  
  441. - setLinkManager:(NXDataLinkManager *)aLinkManager
  442. {
  443.     if (!linkManager) {
  444.     linkManager = aLinkManager;
  445.     [glist makeObjectsPerform:@selector(reviveLink:) with:linkManager];
  446.     [self breakLinkAndRedrawOutlines:nil];
  447.     }
  448.     return self;
  449. }
  450.  
  451. /*
  452.  * This is called when the user chooses Open Source.
  453.  * It uses the trick of drawing directly into the GraphicView
  454.  * which, of course, is only ephemeral since the REAL contents
  455.  * of the GraphicView are stored in the backing store.
  456.  * This is convenient because Open Source is only a temporary
  457.  * the the user calls to see where the data for his link is
  458.  * coming from.
  459.  */
  460.  
  461. - showSelection:(NXSelection *)selection
  462. {
  463.     id retval = self;
  464.     List *graphics = nil;
  465.     NXRect *newInvalidRect;
  466.     NXRect sBounds, linkBounds;
  467.     
  468.     [self lockFocus];
  469.     if (invalidRect) {
  470.     [self drawSelf:invalidRect :1];
  471.     newInvalidRect = invalidRect;
  472.     invalidRect = NULL;
  473.     } else{
  474.     NX_MALLOC(newInvalidRect, NXRect, 1);
  475.     }
  476.     if ([self getRect:&linkBounds forSelection:selection]) {
  477.     PSsetgray(NX_LTGRAY);
  478.     NXFrameRectWithWidth(&linkBounds, 2.0);
  479.     *newInvalidRect = linkBounds;
  480.     graphics = [self findGraphicsInSelection:selection];
  481.     if (graphics) {
  482.         [self getBBox:&sBounds of:graphics];
  483.         NXUnionRect(&sBounds, newInvalidRect);
  484.     } else {
  485.         invalidRect = newInvalidRect;
  486.         [self scrollRectToVisible:invalidRect];
  487.         [window flushWindow];
  488.         retval = nil;
  489.     }
  490.     } else {
  491.     graphics = [self findGraphicsInSelection:selection];
  492.     if (graphics) {
  493.         [self getBBox:&sBounds of:graphics];
  494.         *newInvalidRect = sBounds;
  495.     } else {
  496.         retval = nil;
  497.     }
  498.     }
  499.  
  500.     if (retval) {
  501.     NXFrameLinkRect(&sBounds, NO);
  502.     invalidRect = newInvalidRect;
  503.     NXInsetRect(invalidRect, -NXLinkFrameThickness(), -NXLinkFrameThickness());
  504.     [self scrollRectToVisible:invalidRect];
  505.     [window flushWindow];
  506.     }
  507.  
  508.     [self unlockFocus];
  509.     [graphics free];
  510.  
  511.     return retval;
  512. }
  513.  
  514. /*
  515.  * Called when the Show Links button in the Link Inspector panel is clicked
  516.  * (the link argument will be nil in this case), or when a link is broken
  517.  * (the link argument will be the link that was broken).
  518.  */
  519.  
  520. - breakLinkAndRedrawOutlines:(NXDataLink *)link
  521. {
  522.     int i;
  523.     Graphic *graphic;
  524.     BOOL gotOne = NO;
  525.     NXRect eBounds, recacheBounds;
  526.  
  527.     for (i = [glist count]-1; i >= 0; i--) {
  528.     graphic = [glist objectAt:i];
  529.     if (graphic = [graphic graphicLinkedBy:link]) {
  530.         if (link && ([graphic link] == link) &&
  531.         ([link updateMode] == NX_UpdateNever)) {
  532.             [self removeGraphic:graphic];
  533.         }
  534.         if (!link || [linkManager areLinkOutlinesVisible]) {
  535.         [graphic getExtendedBounds:&eBounds];
  536.         if (gotOne) {
  537.             NXUnionRect(&eBounds, &recacheBounds);
  538.         } else {
  539.             recacheBounds = eBounds;
  540.             gotOne = YES;
  541.         }
  542.         }
  543.     }
  544.     }
  545.     if (gotOne) {
  546.     [self cache:&recacheBounds andUpdateLinks:NO];
  547.     [window flushWindow];
  548.     }
  549.  
  550.     return self;
  551. }
  552.  
  553. /*
  554.  * Tracking Link Changes.
  555.  *
  556.  * This is how we get "Continuous" updating links.
  557.  *
  558.  * We simply assume that a thing someone is linked to in our document
  559.  * changes whenever we have to redraw any rectangle in the GraphicView
  560.  * which intersects the linked-to rectangle.  See cache:andUpdateLinks:
  561.  * in GraphicView.m.
  562.  */
  563.  
  564. typedef struct {
  565.     NXRect linkRect;
  566.     NXDataLink *link;
  567.     BOOL dragged, all;
  568. } LinkRect;
  569.  
  570. - updateTrackedLinks:(const NXRect *)sRect
  571. {
  572.     int i;
  573.     LinkRect *lr;
  574.     List *graphics;
  575.     NXSelection *selection;
  576.     NXRect *lRect, newRect;
  577.  
  578.     for (i = [linkTrackingRects count]-1; i >= 0; i--) {
  579.     if (NXIntersectsRect(sRect, (NXRect *)[linkTrackingRects elementAt:i])) {
  580.         lr = ((LinkRect *)[linkTrackingRects elementAt:i]);
  581.         [lr->link sourceEdited];
  582.         lRect = (NXRect *)[linkTrackingRects elementAt:i];
  583.         if (!lr->dragged && !lr->all && !NXContainsRect(lRect, sRect)) {
  584.         selection = [lr->link sourceSelection];
  585.         if (graphics = [self findGraphicsInSelection:selection]) {
  586.             [self getBBox:&newRect of:graphics];
  587.             *lRect = newRect;
  588.             [graphics free];
  589.         }
  590.         }
  591.     }
  592.     }
  593.  
  594.     return self;
  595. }
  596.  
  597. /* Add to linkTrackingRects. */
  598.  
  599. - startTrackingLink:(NXDataLink *)link
  600. {
  601.     LinkRect trackRect;
  602.     List *graphics = nil;
  603.     NXSelection *selection;
  604.     BOOL all = NO, dragged = NO, piecemeal = NO;
  605.  
  606.     selection = [link sourceSelection];
  607.     if ([selection isEqual:[NXSelection allSelection]]) {
  608.     all = YES;
  609.     trackRect.linkRect = bounds;
  610.     } else if ([self getRect:&trackRect.linkRect forSelection:selection]) {
  611.     dragged = YES;
  612.     } else if (graphics = [self findGraphicsInSelection:selection]) {
  613.     [self getBBox:&trackRect.linkRect of:graphics];
  614.     piecemeal = YES;
  615.     [graphics free];
  616.     } else {
  617.     return nil;
  618.     }
  619.  
  620.     if (all || dragged || piecemeal) {
  621.     if (!linkTrackingRects) {
  622.         linkTrackingRects = [[Storage allocFromZone:[self zone]] initCount:1 elementSize:sizeof(LinkRect) description:"{ffff@}"];
  623.     }
  624.     [self stopTrackingLink:link];
  625.     trackRect.link = link;
  626.     trackRect.dragged = dragged;
  627.     trackRect.all = all;
  628.     [linkTrackingRects addElement:&trackRect];
  629.     }
  630.  
  631.     return nil;
  632. }
  633.  
  634. /* Remove from linkTrackingRects. */
  635.  
  636. - stopTrackingLink:(NXDataLink *)link
  637. {
  638.     int i;
  639.  
  640.     for (i = [linkTrackingRects count]-1; i >= 0; i--) {
  641.     if (((LinkRect *)[linkTrackingRects elementAt:i])->link == link) {
  642.         [linkTrackingRects removeElementAt:i];
  643.         return self;
  644.     }
  645.     }
  646.  
  647.     return nil;
  648. }
  649.  
  650. @end
  651.